package com.hieusua
{
	import com.google.maps.LatLng;
	import com.google.maps.Map;
	import com.google.maps.MapEvent;
	import com.google.maps.controls.ControlPosition;
	import com.google.maps.controls.ZoomControl;
	import com.google.maps.controls.ZoomControlOptions;
	import com.google.maps.interfaces.IControl;
	import com.google.maps.overlays.Marker;
	import com.google.maps.overlays.MarkerOptions;
	
	public class MapHelper
	{
		public static const MAPEVENT_MAPREADY:String = "mapevent_mapready";
		
		// map related
		private var _map:Map;
		private var _center:LatLng;
		
		private var _curMarkerID:int;
		
		// where the markers live
		private var markerRepository:Object;
		private var markerOptRepository:Object;
		
		// map configurations
		private var _scrollEnabled:Boolean = false;
		private var _zoomControlEnabled:Boolean = false;
		private var _zoomControl:IControl;
		private var _zoomLevel:Number;
		
		public function MapHelper( map:Map = null )
		{
			markerRepository = new Object();
			markerOptRepository = new Object();
			_curMarkerID = 0;
			this.map = map;
			
		}
		
		/**
		 * This function will be called whenever map object is changed
		 * 
		 */
		public function init():void {
			if ( _map != null ) {
				if ( _map.isLoaded() ) {
					commitEnableScroll();
					commitEnableZoomControl();
					commitZoomLevel();
					commitCenter();
					for each ( var wrapper:MarkerWrapper in markerRepository ) {
						commitMarkerCreation( wrapper );
					}
				}
				else {
					_map.addEventListener( MapHelper.MAPEVENT_MAPREADY, mapReadyHandler );
				}
			} 
		}
		
		private function mapReadyHandler( event:MapEvent ):void {
			_map.removeEventListener( MapHelper.MAPEVENT_MAPREADY, mapReadyHandler );
			init();
		}
		
		/**
		 * This function clears stuffs up before release the map
		 * 
		 */
		public function cleanup():void {
			clearMarkers( Number.MAX_VALUE );
		}
		
		public function clearMarkers( level:Number = 0 ):void {
			if ( mapReady ) {
				for each ( var wrapper:MarkerWrapper in markerRepository ) {
					if ( wrapper.level <= level )
						_map.removeOverlay( wrapper.marker );
				}
			}
			markerRepository = new Object();
			markerOptRepository = new Object();
		}
		
		/**
		 * 
		 * @return the status of the map
		 * 
		 */
		public function get mapReady():Boolean {
			return ( _map != null && _map.isLoaded() );
		}
		
		/**
		 * 
		 * @param value the map that this helper will help
		 * 
		 */
		public function set map( value:Map ):void {
			if ( value == null ) return;
			if ( this._map != null ) { 
				cleanup();			
			}
			this._map = value;
			init();
		}
		
		/**
		 * 
		 * @return the map that this helper is helping 
		 * 
		 */
		public function get map():Map {
			return _map;
		}
		
		/**
		 * 
		 * Enable/disable scroll wheel zoom effect
		 * 
		 */
		public function set enableScroll( value:Boolean ):void {
			_scrollEnabled = value;
			commitEnableScroll();
		}
		
		private function commitEnableScroll():void {
			if ( mapReady ) {
				if ( _scrollEnabled ) {
					_map.enableScrollWheelZoom();
					_map.enableContinuousZoom();
				}
				else
					_map.disableScrollWheelZoom();
					
			}
		}
		
		/**
		 * 
		 * @return scroll wheel enabled
		 * 
		 */
		public function get enableScroll():Boolean {
			return _scrollEnabled;
		}
		
		/**
		 * 
		 * Turn on/off zoom control
		 * 
		 */
		public function set enableZoomControl( value:Boolean ):void {
			_zoomControlEnabled = value;
			commitEnableZoomControl();
		}
		
		private function commitEnableZoomControl():void {
			if ( mapReady ) {
				if ( _zoomControlEnabled ) {
					_zoomControl = new ZoomControl( 
										new ZoomControlOptions( 
											{ position: new ControlPosition( ControlPosition.ANCHOR_TOP_LEFT, 10, 10 ) } 
										) 
									);
					map.addControl( _zoomControl );
				}
				else if ( _zoomControl != null ) {
					map.removeControl( _zoomControl );
				}
			}
		}
		
		/**
		 * 
		 * @return the zoom control's status
		 * 
		 */
		public function get enableZoomControl():Boolean {
			return _zoomControlEnabled;
		}
		
		/**
		 * Set zoom level
		 * @param value
		 * 
		 */
		public function set zoomLevel( value:Number ):void {
			_zoomLevel = value;
			commitZoomLevel();
		}
		
		private function commitZoomLevel():void {
			if ( mapReady )
				_map.setZoom( _zoomLevel );
		}
		
		/**
		 * Get zoom level 
		 * @return 
		 * 
		 */
		public function get zoomLevel():Number {
			if ( mapReady )
				return _map.getZoom();
			else
				return -1;
		}
		
		/**
		 * 
		 * Set center of the map 
		 * 
		 */
		public function setCenter( lat:Number, lng:Number ):void {
			_center = new LatLng( lat, lng );
			commitCenter();
		}
		
		/**
		 * 
		 * Go to the position
		 * 
		 */
		public function setCenter2( pos:LatLng ):void {
			if ( pos == null ) return;
			_center = pos;
			commitCenter();
		}
		
		/**
		 * 
		 * Go to the marker's position
		 * 
		 */
		public function goToMarker( id:String ):void {
			if ( markerRepository[id] == null ) return;
			_center = MarkerWrapper( markerRepository[id] ).latlng;
			commitCenter();
		}
		
		/**
		 * 
		 * Pan to the position
		 * 
		 */
		public function panTo( pos:LatLng ):void {
			if ( pos != null && mapReady ) {
				_map.panTo( pos );
			}
		}
		
		/**
		 * 
		 * Pan to the marker's position
		 * 
		 */
		public function panToMarker( id:String ):void {
			if ( markerRepository[id] != null && mapReady ) {
				_map.panTo( MarkerWrapper( markerRepository[id] ).latlng );
			}
		}
		
		/**
		 * 
		 * Function for mxml declaration
		 * 
		 */
		public function set mxmlCenter( value:String ):void {
			var arrParsingParams:Array = value.split( " " );
			if ( arrParsingParams != null && arrParsingParams != [] ) {
				if ( !isNaN( Number( arrParsingParams[0] ) ) && !isNaN( Number( arrParsingParams[1] ) ) ) {
					setCenter( Number( arrParsingParams[0] ), Number( arrParsingParams[1] ) );
				}
			}
		}
		
		private function commitCenter():void {
			if ( mapReady ) {
				_map.setCenter( _center, _zoomLevel );
			}
		}
		
		/**
		 * 
		 * @return the map's center 
		 * 
		 */
		private function getCenter():LatLng {
			if ( _center == null )
				_center = new LatLng( 0, 0 );
			return _center;	
		}
		
		
		/*-------------------------
		Marker section
		-------------------------*/
		/**
		 * Generate a unique ID for each marker 
		 * @return 
		 * 
		 */
		public function generateMarkerID():String {
			while ( markerRepository[_curMarkerID.toString()] != null ) {
				_curMarkerID++;
			}
			return _curMarkerID.toString();
		}
		
		/**
		 * CREATE
		 *  
		 * @param lat
		 * @param lng
		 * @return 
		 * 
		 */
		public function createMarker( lat:Number, lng:Number, visible:Boolean = false, markerOptId:String = "", level:Number = 0 ):String {
			var newMarker:MarkerWrapper = new MarkerWrapper( generateMarkerID() );
			
			newMarker.latlng = new LatLng( lat, lng );
			newMarker.level = level;
			//newMarker.lat = lat;
			//newMarker.lng = lng;
			newMarker.visible = visible;
			if ( mapReady ) {
				newMarker.marker = new Marker( newMarker.latlng/* new LatLng( newMarker.lat, newMarker.lng ) */ );
			}
			markerRepository[ newMarker.id ] = newMarker;
			
			applyOptionsToMarker( newMarker.id, markerOptId );
			
			commitMarkerCreation( newMarker );
			
			return newMarker.id;
		}
		
		private function commitMarkerCreation( wrapper:MarkerWrapper ):void {
			if ( mapReady && wrapper.marker == null ) {
				wrapper.marker = new Marker( wrapper.latlng/* new LatLng( wrapper.lat, wrapper.lng ) */ );
				commitMarkerVisibility( wrapper );
				commitMarkerLatLng( wrapper );
				commitMarkerForm( wrapper );
			}
		}
		
		/**
		 * READ 
		 * @param id
		 * @return 
		 * 
		 */
		public function getMarker( id:String ):Marker {
			return ( markerRepository[id] == null ? null : markerRepository[id].marker );
		}
		
		public function getMarkerVisible( id:String ):Boolean {
			return ( markerRepository[id] == null ? false : markerRepository[id].visible );
		}
		
		public function getMarkerLatLng( id:String ):LatLng {
			return ( markerRepository[id] == null ? null : markerRepository[id].latlng );
		}
		
		/**
		 * UPDATE 
		 * 
		 */
		public function setMarkerVisible( id:String, value:Boolean ):void {
			var wrapper:MarkerWrapper = markerRepository[id];
			if ( wrapper != null ) {
				wrapper.visible = value;
				commitMarkerVisibility( wrapper );
			}
		}
		
		private function commitMarkerVisibility( wrapper:MarkerWrapper ):void {
			if ( mapReady ) {
				if ( wrapper.marker == null ) return;
				if ( wrapper.visible )
					_map.addOverlay( wrapper.marker );
				else
					_map.removeOverlay( wrapper.marker );
			}
		}
		
		public function setMarkerLat( id:String, lat:Number ):void {
			var wrapper:MarkerWrapper = markerRepository[id];
			if ( wrapper == null ) return;
			wrapper.lat = lat;
			commitMarkerLatLng( wrapper );
		}
		
		public function setMarkerLng( id:String, lng:Number ):void {
			var wrapper:MarkerWrapper = markerRepository[id];
			if ( wrapper == null ) return;
			wrapper.lng = lng;
			commitMarkerLatLng( wrapper );
		}
		
		public function setMarkerLatLng( id:String, lat:Number, lng:Number ):void {
			var wrapper:MarkerWrapper = markerRepository[id];
			if ( wrapper == null ) return;
			wrapper.latlng = new LatLng( lat, lng );
			/* wrapper.lat = lat;
			wrapper.lng = lng; */
			commitMarkerLatLng( wrapper );
		}
		
		private function commitMarkerLatLng( wrapper:MarkerWrapper ):void {
			if ( wrapper.marker == null ) return;
			wrapper.marker.setLatLng( wrapper.latlng/* new LatLng( wrapper.lat, wrapper.lng ) */ );
		}
		
		
		
		/*---------------------------
			Marker Options section
		---------------------------*/
		/**
		 * Register a new markerOptions with a unique name 
		 * @param id
		 * @param value
		 * @return 
		 * 		false if this id is already taken
		 * 
		 */
		public function registerMarkerOptions( id:String, value:Object ):Boolean {
			if ( markerOptRepository[id] != null ) return false;
			
			var wrapper:MarkerOptionsWrapper = new MarkerOptionsWrapper( id );
			if ( value is MarkerOptions )
				wrapper.markerOpt = value as MarkerOptions;
			else
				wrapper.markerOpt = new MarkerOptions( value );
			markerOptRepository[id] = wrapper;
			
			return true;
		}
		
		public function getMarkerOptions( id:String ):MarkerOptions {
			
			return MarkerOptionsWrapper(markerOptRepository[id]).markerOpt;
		}
		
		
		/**
		 * Set marker options for a marker 
		 * @param markerId
		 * @param markerOptId
		 * @return the result status of the function
		 * 
		 */
		public function applyOptionsToMarker( markerId:String, markerOptId:String ):Boolean {
			// check if related marker and markerOpts are really existed
			var markerWrapper:MarkerWrapper = markerRepository[markerId];
			var markerOptWrapper:MarkerOptionsWrapper = markerOptRepository[markerOptId];
			if ( markerWrapper == null || markerOptWrapper == null )
				return false;
			// clear old bond
			markerWrapper.optWrapper = markerOptWrapper;
			commitMarkerForm( markerWrapper );
			return true;
		}
		
		private function commitMarkerForm( wrapper:MarkerWrapper ):void {
			if ( wrapper.marker == null || wrapper.optWrapper == null ) return;
			wrapper.marker.setOptions( wrapper.optWrapper.markerOpt );
		}
		
		/**
		 * Re-configure a specific marker options 
		 * @param markerOptId
		 * @param value
		 * 
		 */
		public function updateMarkerOptions( markerOptId:String, value:Object ):void {
			var markerOptWrapper:MarkerOptionsWrapper = markerOptRepository[markerOptId];
			if ( markerOptWrapper == null ) return;
			if ( value is MarkerOptions )
				markerOptWrapper.markerOpt = value as MarkerOptions;
			else
				markerOptWrapper.markerOpt = new MarkerOptions( value );
			for each ( var wrapper:MarkerWrapper in markerRepository ) {
				if ( wrapper.isBinding( markerOptId ) )
					commitMarkerForm( wrapper );
			}
		}
		
	}
}